/*
*    UNIVERSIDADE DE SÃO PAULO.
*    Author: Marco Aurélio Gerosa (gerosa@ime.usp.br)
*
*    This file is part of Groupware Workbench (http://www.groupwareworkbench.org.br).
*
*    Groupware Workbench is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    Groupware Workbench is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public License
*    along with Swift.  If not, see <http://www.gnu.org/licenses/>.
*/
package br.org.groupwareworkbench.collablet.coop.recommend;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Element;
import org.dom4j.Node;

import br.org.groupwareworkbench.core.exceptions.ConfigurationException;

public class XmlRecommendConfig {

    Map<Class<?>,ClassInfo> classes = new HashMap<Class<?>, ClassInfo>();
    List<Node> listNodesXml = new ArrayList<Node>();
    
    @SuppressWarnings("unchecked")
    public XmlRecommendConfig(Element xml) {
        
        List<Node> listNodes = xml.selectNodes("recommend") ;
        listNodesXml = listNodes;
        
        for (Node recommendNode : listNodes) {
            String className = recommendNode.valueOf("@className");
            List<Node> listMethodNode = recommendNode.selectNodes("method");
            for (Node methodNode : listMethodNode) {
                String methodName = methodNode.valueOf("@name");
                boolean enabled = Boolean.parseBoolean(methodNode.valueOf("@enabled"));
                mapClass(className, methodName, enabled);
            }
        }
    }

    private void mapClass(String className, String methodName, boolean enabled) {
        Class<?> entityClass;
        try {
            entityClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new ConfigurationException(e);
        }
        Method method = findMethod(entityClass, methodName);
        mapClass(entityClass, method, enabled);
    }

    private Method findMethod(Class<?> entityClass, String methodName) {
        Method method;
        try {
            method = entityClass.getMethod(methodName); 
        } catch (NoSuchMethodException e) {
            throw new ConfigurationException(e);
        }
        if (Modifier.isStatic(method.getModifiers())) {
            throw new ConfigurationException("The method for the class " + entityClass.getName() + " is static, cannot be used for recommendations.");
        }
        return method;
    }

    private void mapClass(Class<?> entityClass, Method method, boolean enabled) {
        if (!classes.containsKey(entityClass)){
            ClassInfo clazz = new ClassInfo();
            clazz.setName(entityClass.getSimpleName());
            clazz.setPackageName(entityClass.getPackage().getName());
            classes.put(entityClass, clazz);
        }
        ClassInfo clazz = classes.get(entityClass);
        if (!clazz.getMethods().containsKey(method)){
            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setName(method.getName());
            methodInfo.setEnabled(enabled);
            clazz.getMethods().put(method, methodInfo);
        }
        clazz.getMethods().get(method).setEnabled(enabled);
    }

    public List<String> extractMethods(Object object) {
        List<String> toReturn =  new ArrayList<String>();
        ClassInfo classInfo = null;
        for (Class<?> clazz = object.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
            classInfo = classes.get(clazz);
            if (classInfo != null) break;
        }
        if (classInfo == null) throw new IllegalArgumentException("The class " + object.getClass() + " (nor any of it's superclasses) was not configured for recommendations in the XML.");
        for (Method method : classInfo.getMethods().keySet()) {
            MethodInfo methodInfo = classInfo.getMethods().get(method);
            if (methodInfo.isEnabled()) {
                String value = null;
                try {
                    value = (String) method.invoke(object);
                } catch (InvocationTargetException e) {
                    throw new ConfigurationException(e);
                } catch (IllegalAccessException e) {
                    throw new AssertionError();
                }
                if (value != null) {
                    toReturn.add(value.replaceAll("<+[A-Za-z0-9._%+-/\\\"\'= ]+>", " "));
                }
            }
        }
        return toReturn;
    }

     public List<ClassInfo> listClassByPackage(String packageName) {
         List<ClassInfo> toReturn = new ArrayList<ClassInfo>();

         for (ClassInfo clazz : classes.values()) {
             if (packageName.equals(clazz.getPackageName())){
                 toReturn.add(clazz);
             }
         }
         return toReturn;
     }
     
     public void updateStateOfMethod(String packageName, List<String> enabledMethodList) {
         for (ClassInfo clazz : classes.values()) {
             if (packageName.equals(clazz.getPackageName())){
                 for ( MethodInfo methodInfo : clazz.getMethods().values() ) {
                     if (enabledMethodList.contains(methodInfo.getName()))
                         methodInfo.setEnabled(true);
                     else 
                         methodInfo.setEnabled(false);
                 } 
             }
         }
     }
     
}